package com.linkallcloud.log.core.aop;

import java.lang.reflect.Method;
import java.util.Date;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

import com.alibaba.fastjson2.JSON;
import com.linkallcloud.core.annotation.LacLog;
import com.linkallcloud.core.aop.DomainDescription;
import com.linkallcloud.core.aop.LacAspect;
import com.linkallcloud.core.domain.IDomain;
import com.linkallcloud.core.face.message.request.ObjectFaceRequest;
import com.linkallcloud.core.lang.Mirror;
import com.linkallcloud.core.lang.Stopwatch;
import com.linkallcloud.core.lang.Strings;
import com.linkallcloud.core.log.Log;
import com.linkallcloud.core.log.Logs;
import com.linkallcloud.log.core.domain.BusiLog;

public abstract class LogAspect<T extends BusiLog> extends LacAspect {
    //存储日志的时候请从slog输出
    protected Log slog = Logs.get();

    // public static TransmittableThreadLocal<BusiLog> logMessageThreadLocal = new
    // TransmittableThreadLocal<>();

    protected Mirror<T> logMirror;

    /**
     * 是否记录非LacLog注解的方法日志
     */
    protected boolean storageAll;

    @SuppressWarnings("unchecked")
    public LogAspect() {
        super();
        this.storageAll = false;
        try {
            logMirror = Mirror.me((Class<T>) Mirror.getTypeParams(getClass())[0]);
        } catch (Throwable e) {
            systemErrorLog(0, null, null, null, e, "Fail to get TypeParams for self!");
        }
    }

    public LogAspect(boolean storageAll) {
        this();
        this.storageAll = storageAll;
    }

    protected abstract void logStorage(T operatelog) throws Exception;

    protected abstract void subclassDealLogs(T operatelog, JoinPoint joinPoint, Class<?> clzz, Method method,
            LacLog logAnnot);

    protected T createBusiLog(JoinPoint joinPoint, Method method) {
        T busiLog = logMirror.born();
        Class<?> clzz = joinPoint.getTarget().getClass();
        busiLog.setClassName(clzz.getName());
        String tid = dealTid(joinPoint, method, true);
        busiLog.setTid(tid);
        busiLog.setMethodName(joinPoint.getSignature().getName());

        try {
            Object[] ob = getValidateArgs(joinPoint);
            busiLog.setMethodParameters(JSON.toJSONString(ob));
        } catch (Exception e) {
        }
        busiLog.setOperateTime(new Date());
        return busiLog;
    }

    /**
     * 保存系统操作日志
     *
     * @param joinPoint
     *            连接点
     * @return 方法执行结果
     * @throws Throwable
     *             调用出错
     */
    protected Object autoLog(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getMethod(joinPoint);
        T operatelog = createBusiLog(joinPoint, method);
        Stopwatch sw = Stopwatch.begin();
        Object result = "";

        LacLog logAnnot = method.getAnnotation(LacLog.class);
        if (logAnnot == null) {
            try {
                sw.start();
                result = joinPoint.proceed();
                if (this.storageAll) {
                    sw.stop();
                    operatelog.setMethodResult(JSON.toJSONString(result));
                    autoDealLogs(operatelog, sw.getDuration(), joinPoint, method, null, null);
                }
                return result;
            } catch (Throwable e) {
                sw.stop();
                operatelog.setOperateDesc("执行方法（" + method.getName() + "）出错啦");
                autoDealLogs(operatelog, sw.getDuration(), joinPoint, method, logAnnot, e);
                throw e;
            }
        } else {
            try {
                sw.start();
                result = joinPoint.proceed();
                sw.stop();
                operatelog.setMethodResult(JSON.toJSONString(result));
                autoDealLogs(operatelog, sw.getDuration(), joinPoint, method, logAnnot, null);
                return result;
            } catch (Throwable e) {
                sw.stop();
                autoDealLogs(operatelog, sw.getDuration(), joinPoint, method, logAnnot, e);
                throw e;
            }
        }
    }

    private void autoDealLogs(T operatelog, long duration, ProceedingJoinPoint joinPoint, Method method,
            LacLog logAnnot, Throwable e) {
        try {
            Class<?> clzz = joinPoint.getTarget().getClass();

            if (Strings.isBlank(operatelog.getTid())) {
                operatelog.setTid(dealTid(joinPoint, method, true));
            }

            operatelog.setModule(getModuleName(clzz));
            operatelog.setCostTime(duration);
            operatelog.setOperateResult(e == null ? 0 : 1);
            if (operatelog.getOperateTime() == null) {
                operatelog.setOperateTime(new Date());
            }

            operatelog.setError(e);
            if (e != null) {
                // String errorMessage = LogExceptionStackTrace.erroStackTrace(e).toString();
                String errorMessage = e.getMessage();
                if (!Strings.isBlank(errorMessage)) {
                    operatelog.setErrorMessage(errorMessage.replaceAll("[\t\n\r]", ""));
                }
                operatelog.setLogLevel("ERROR");
            } else {
                operatelog.setLogLevel("INFO");
            }

            if (logAnnot != null && Strings.isBlank(logAnnot.desc())) {// 用户没有定义，后续尝试自动处理
                operatelog.setSaveTag(getSaveMethodTag(joinPoint, method));
            }

            subclassDealLogs(operatelog, joinPoint, clzz, method, logAnnot);

            logStorage(operatelog);
        } catch (Throwable er) {
            operatelog.setError(er);
            operatelog.setErrorMessage("自动处理日志失败，可能是日志存储失败");
            operatelog.setLogLevel("ERROR");
            slog.info(JSON.toJSONString(operatelog));
        }
    }

    protected void systemErrorLog(long duration, JoinPoint joinPoint, Method method, LacLog logAnnot, Throwable e,
            String errorDesc) {
        try {
            T sysLog = logMirror.born();
            if (joinPoint != null && method != null) {
                Class<?> clzz = joinPoint.getTarget().getClass();
                sysLog.setClassName(clzz.getName());
                String tid = dealTid(joinPoint, method, true);
                sysLog.setTid(tid);
                sysLog.setMethodName(joinPoint.getSignature().getName());
                try {
                    Object[] ob = getValidateArgs(joinPoint);
                    sysLog.setMethodParameters(JSON.toJSONString(ob));
                } catch (Exception ex) {
                }

                subclassDealLogs(sysLog, joinPoint, clzz, method, logAnnot);
            } else {
                sysLog.setClassName("com.linkallcloud.core.busilog.LacLogAspect");
                sysLog.setMethodName("LacLogAspect()");
                sysLog.setOperateDesc(errorDesc);
            }
            sysLog.setOperateTime(new Date());
            sysLog.setCostTime(duration);

            sysLog.setOperateResult(1);
            sysLog.setError(e);
            sysLog.setLogLevel("ERROR");
            if (e != null) {
                // String errorMessage = LogExceptionStackTrace.erroStackTrace(e).toString();
                String errorMessage = e.getMessage();
                if (!Strings.isBlank(errorMessage)) {
                    sysLog.setErrorMessage(errorMessage.replaceAll("[\t\n\r]", ""));
                }
            }

            try {
                logStorage(sysLog);
            } catch (Throwable e1) {
                sysLog.setErrorMessage("日志存储失败");
                slog.info(JSON.toJSONString(sysLog));
            }
        } catch (Throwable er) {
        }
    }

    /**
     * 得到save方法是否是新增还是更新的标志
     *
     * @param joinPoint
     * @param method
     * @return save4NewTag ,是否save方法，若是的话1：新增；2：更新，其它0
     */
    private int getSaveMethodTag(ProceedingJoinPoint joinPoint, Method method) {
        DomainDescription dd = getDomainDescription(joinPoint);
        if (dd != null && method.getName().startsWith("save")) {
            Mirror<?> domainMirror = null;
            for (Object arg : joinPoint.getArgs()) {
                domainMirror = Mirror.me(arg);
                if (domainMirror.is(dd.getDomainClass())) {
                    return getDomainSaveTage((IDomain) arg, domainMirror);
                } else if (domainMirror.is(ObjectFaceRequest.class)) {
                    Object data = ((ObjectFaceRequest<?>) arg).getData();
                    if (data instanceof IDomain) {
                        return getDomainSaveTage((IDomain) data, null);
                    }
                }
            }
        }
        return 0;
    }

    private int getDomainSaveTage(IDomain domain, Mirror<?> domainMirror) {
        int save4NewTag = 0;
        if (domain != null) {
            if (domainMirror == null) {
                domainMirror = Mirror.me(domain);
            }
            Object fieldValue = domainMirror.getValue(domain, "id");
            if (fieldValue == null) {
                save4NewTag = 1;
            } else if (fieldValue.getClass().equals(Long.class)) {
                Long id = (Long) fieldValue;
                if (id.longValue() <= 0) {
                    save4NewTag = 1;
                } else {
                    save4NewTag = 2;
                }
            } else {
                save4NewTag = 2;
            }
        }
        return save4NewTag;
    }

    public boolean isStorageAll() {
        return storageAll;
    }

    public void setStorageAll(boolean storageAll) {
        this.storageAll = storageAll;
    }

}
